#include "modbus_setting.hpp"

#if ENABLE_MODBUS == 1
#include <sstream>
#include <algorithm>
#include <memory>
#include "tinyxml.h"
#include "config.hpp"
#include "defines.hpp"
#include "crc.hpp"
#include "misc.h"
////////////////////////////////////////////////////////////////////////////////////////
void ModbusSetting :: stItem :: make_cmd()
{
	cmd[ MODBUS_SITE ]      = (uint8_t) site;
	cmd[ MODBUS_CMD ]       = (uint8_t) opt;
	cmd[ MODBUS_REG_H ]     = (uint8_t)( (start_reg >> 8) & 0xFF );
	cmd[ MODBUS_REG_L ]     = (uint8_t)( start_reg & 0xFF );
	cmd[ MODBUS_COUNT_H ]   = (uint8_t)( ( count >> 8) && 0xff );
	cmd[ MODBUS_COUNT_L ]   = (uint8_t)( count & 0xff );
	// 生成MODBUS CRC，完成MDOBUS
	uint16_t crc = CRC :: MODBUS( cmd , 6 );
	cmd[ MODBUS_CRC_L ]     = (uint8_t)( crc >> 8 & 0xff );
	cmd[ MODBUS_CRC_H ]     = (uint8_t)( crc & 0xff );

	cmd_len = 8;
}

bool ModbusSetting :: stItem :: 
CloneCmd( char * data , size_t& len ) const
{
	if( data == nullptr ) return false;
	bool ret = id == -1 ? true : false;
	
	len = cmd_len;
	try{ 
		if( cmd_len > 0 ){
			memcpy( data , cmd , cmd_len );
		}
	}catch( ... ){ // 如果data访问越界则会抛出异常
		ret = false;
		__show_line_1( "%s%s%s" , TRED , "Data copy maybe out of range." , TNORMAL );
	}

	return ret;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
ModbusSetting :: ModbusSetting()
{
    	m_curr_id = 0;
}

ModbusSetting :: ModbusSetting( const std::string& file )
{
	if( file.empty() ){
		throw ERR_FILE_NAME_EMPTY;
	}
	m_curr_id = 0;
	init_from_file( file );
}

ModbusSetting :: ~ModbusSetting()
{
    	__show_line_2();
}

const ModbusSetting :: ModMission & 
ModbusSetting :: Mission() const
{
    	return m_data;
}

ModbusSetting::err_code 
ModbusSetting :: Mission( int opt , int site , int reg , int count , long intval )
{
	err_code ret = OK;
	try{
		stItem  item;
		item( m_curr_id , opt , site , reg , count , intval );
		m_data.push_back( item );
		m_curr_id ++;
	}catch( ... ){
		ret = ERR_ALLOC_MEM;
	}
    return ret;
}

ModbusSetting :: err_code 
ModbusSetting :: Del( int id )
{
	auto it = std::find_if( m_data.begin() , m_data.end() , [this , id ]( const stItem& item ){
		return item.id == id;
	});

	if( it != m_data.end() ){
		m_data.erase( it );
		re_alloc_id();
		m_curr_id --;
	}else{
		return ERR_CAN_NOT_FIND_MISSION;
	}

	return OK;
}

void ModbusSetting :: re_alloc_id( )
{
	int i = 0;
	for( auto it = m_data.begin();  it != m_data.end(); it ++ , i ++ ){
		it->id = i;
	}
}

void ModbusSetting :: init_from_file( const std::string& file )
{
	try{
		
		if( file.empty() ) return;
		CConfFile   var( file );
		
		int  count = 0;
		char tmp_buf[ 50 ];

		var.GetValue("/auto_id" , m_curr_id );          // 
		var.GetValue( "/count" , count );               // 读取任务列表的数量
		for( int i = 0; i < count; i ++ ){
			stItem  item;
			int o = -1 , s = -1 , r = -1 , c = -1 , it = -1;
			sprintf( tmp_buf , "/%d" , i );
			std :: string str( tmp_buf );
		
			var.GetValue( str + "/opt"      , o  );
			var.GetValue( str + "/site"     , s  );
			var.GetValue( str + "/start"    , r  );
			var.GetValue( str + "/count"    , c  );
			var.GetValue( str + "/interval" , it );

			item( i , o , s , r , c ,it );

			m_data.push_back( item );
		}
	}catch( CConfFile::err_code e ){
		show_err_msg( e );
	}catch( std::bad_alloc e ){
		__show_line_1( "%s%s%s" , TRED , "Allocate memory fail." , TNORMAL );
		abort();
	}
}

ModbusSetting :: err_code 
ModbusSetting :: Save( const std::string& file )
{
	// 生成XML DOM 文件对象
	TiXmlDocument   doc;
	// 添加数据
	to_xml( doc );
	
	// 保存文件
	doc.SaveFile( file );
	return OK;
}

bool ModbusSetting :: prepare_xml_file( TiXmlDocument& doc )
{
	try{
		TiXmlDeclaration * decl = new TiXmlDeclaration("1.0" , "UTF-8" , "yes" );

		doc.LinkEndChild( decl );

		// 添加根
		TiXmlElement * p_root = new TiXmlElement( "CONF_FILE" );
		p_root->SetAttribute( "VER" , "1.0" );
		p_root->SetAttribute( "author" , "" );

		doc.LinkEndChild( p_root );

	}catch( std::bad_alloc& e ){
		__show_line_1( "%s Allocate memory fail.%s" , TRED , TNORMAL );
		return false;
	}
	return true;
}

bool ModbusSetting :: add_file_desc( TiXmlDocument& doc )
{
	TiXmlElement  *p_ele = doc.RootElement() , *p_desc = nullptr;

	if( p_ele ){
		try{
			p_desc = new TiXmlElement( "var" );

			p_desc->SetAttribute( "name" , "auto_id" );
			p_desc->SetAttribute( "type" , "int"     );
			std::stringstream ss;
			ss << m_curr_id;
			p_desc->SetAttribute( "value", ss.str()  );
			p_ele->LinkEndChild( p_desc );
			ss.str("");   ss.clear();
			p_desc = new TiXmlElement( "var" );
			p_desc->SetAttribute( "name" , "count"   );
			p_desc->SetAttribute( "type" , "int"     );
			// 设置当前记录数量
			ss << m_curr_id;
			
			p_desc->SetAttribute( "value", ss.str()  );
			
			p_ele->LinkEndChild( p_desc );
			
			return true;
		}catch( std::bad_alloc & e ){
			__show_line_1( "%s Allocate memory fail.%s" , TRED , TNORMAL );
			return false;
		}
	}else{
		__show_line_2();
		return false;
	}

    return true;
}

void ModbusSetting :: to_xml( TiXmlDocument& doc )
{
	// 添加根节点
	TiXmlElement  *p_ele = doc.RootElement();
	TiXmlElement  *p_dir = nullptr;
	
	if( p_ele == nullptr ){
		if( prepare_xml_file( doc ) == false ){
			__show_line_1( "%s Initialize modbus mission recorder file fail%s" , TRED , TNORMAL );
			m_error = ERR_PREPARE_MISSION_FILE;
		return;
		}else{ 
			m_error = OK; 
			p_ele = doc.RootElement();
		}
	}
	
	// 添加文件描述类节点
	if( add_file_desc( doc ) == false ){
		m_error = ERR_ADD_DESC_NODES;
		return;
	}else{ m_error = OK;}
	
	// 添加内容节点
	bool flag = true;
	int i = 0;
	for( auto it = m_data.begin(); it < m_data.end() && flag == true; it ++ , i ++){
		p_dir = add_mission_dir( p_ele , i );
		
		if( p_dir ){
			flag = add_mission( p_dir , it );

			if( flag == false ){        // 添加任务失败
				m_error = ERR_ADD_MISSION;
			}
		}else{ 
			flag = false; 
			m_error = ERR_ADD_MISSION_DIR;
		}
	}// for( auto it = m_data.begin)
}

TiXmlElement * ModbusSetting :: add_mission_dir( TiXmlElement * root , int i )
{
	if( root == nullptr ) return nullptr;
	if( i < 0 ) return nullptr;

	TiXmlElement * ret = nullptr;
	try{
		ret = new TiXmlElement( "dir" );

		std::stringstream ss;
		ss << i ;
		ret->SetAttribute( "name" , ss.str() );

		root->LinkEndChild( ret );

	}catch( std::bad_alloc& e ){
		__show_line_1( "%s Allocate memory fail.%s" , TRED , TNORMAL );
		ret = nullptr;
	}

	return ret;
}

bool ModbusSetting :: add_mission( TiXmlElement* dir , ModMission::iterator it )
{
	bool ret = false;

	if( dir == nullptr ) return false;
	if( it == m_data.end() ) return false;

	try{
		TiXmlElement * p_ele = new TiXmlElement( "var" );

		p_ele->SetAttribute( "name" , "opt" );
		p_ele->SetAttribute( "type" , "int" );
		std::stringstream ss;
		ss << it->opt;
		p_ele->SetAttribute( "value" , ss.str() );

		dir->LinkEndChild( p_ele );
		ss.clear(); ss.str("");
/////////////////////////////////////////////////////////////////////////////////////////////////////////
		p_ele = new TiXmlElement( "var" );

		p_ele->SetAttribute( "name" , "site" );
		p_ele->SetAttribute( "type" , "int" );
		ss << it->site;
		p_ele->SetAttribute( "value" , ss.str() );

		dir->LinkEndChild( p_ele );
		ss.clear(); ss.str("");
/////////////////////////////////////////////////////////////////////////////////////////////////////////
		p_ele = new TiXmlElement( "var" );

		p_ele->SetAttribute( "name" , "start" );
		p_ele->SetAttribute( "type" , "int" );
		ss << it->start_reg;
		p_ele->SetAttribute( "value" , ss.str() );

		dir->LinkEndChild( p_ele );
		ss.clear(); ss.str("");
/////////////////////////////////////////////////////////////////////////////////////////////////////////
		p_ele = new TiXmlElement( "var" );

		p_ele->SetAttribute( "name" , "count" );
		p_ele->SetAttribute( "type" , "int" );
		ss << it->count;
		p_ele->SetAttribute( "value" , ss.str() );

		dir->LinkEndChild( p_ele );
		ss.clear(); ss.str("");
/////////////////////////////////////////////////////////////////////////////////////////////////////////
		p_ele = new TiXmlElement( "var" );

		p_ele->SetAttribute( "name" , "interval" );
		p_ele->SetAttribute( "type" , "int" );
		ss << it->interval;
		p_ele->SetAttribute( "value" , ss.str() );

		dir->LinkEndChild( p_ele );

        	ret = true;
	}catch( std::bad_alloc& e ){
		__show_line_1( "%s Allocate memory fail.%s" , TRED , TNORMAL );
		ret = false;
	}
	return ret;
}

void ModbusSetting :: show_err_msg( CConfFile::err_code e )
{
	switch( e ){
		case CConfFile::CAN_NOT_LOAD_FILE:
		__show_line_1( "%s%s%s" , TRED , "Can not load system variable from file" , TNORMAL );
		break;
		case CConfFile::CAN_NOT_FIND_XML_ROOT:
		__show_line_1( "%s%s%s" , TRED , "XML file format error , can not find root node" , TNORMAL );
		break;
		case CConfFile::ERROR_FILE_VERSION:
		__show_line_1( "%s%s%s" , TRED , "System variable file version check fail." , TNORMAL );
		break;
		case CConfFile::ERROR_FILE_FORMAT:
		__show_line_1( "%s%s%s" , TRED , "System variable file format fail." , TNORMAL );
		break;
		case CConfFile::ERROR_FILE_ROOT_NAME:
		__show_line_1( "%s%s%s" , TRED , "System variable file root node name fail" , TNORMAL );
		break;
		case CConfFile::ERROR_VAR_NO_NAME:
		case CConfFile::ERR_VAR_NAME_EMPTY:
		__show_line_1( "%s%s%s" , TRED , "Missing variable name" , TNORMAL );
		break;
		case CConfFile::ERROR_VAR_VALUE_TYPE:
		__show_line_1( "%s%s%s" , TRED , "Variable value type dismatch" , TNORMAL );
		break;
		case CConfFile::ERROR_FILE_DIR_FORMAT:
		__show_line_1( "%s%s%s" , TRED , "Directory format fail." , TNORMAL );
		break;
		case CConfFile::ERROR_VAR_NOT_EXIST:
		__show_line_1( "%s%s%s" , TRED , "Variable not exist" , TNORMAL );
		break;
		case CConfFile::ERROR_SAVE_FILE:
		__show_line_1( "%s%s%s" , TRED , "Save file fail" , TNORMAL );
		break;
		case CConfFile::ERR_PATH_GRRAMA:
		__show_line_1( "%s%s%s" , TRED , "path description gramma error" , TNORMAL );
		break;
		case CConfFile::ERR_PATH_NOT_EXIST:
		__show_line_1( "%s%s%s" , TRED , "Path not exist." , TNORMAL );
		break;
		case CConfFile::ERR_DEL_ROOT:
		__show_line_1( "%s%s%s" , TRED , "Can not delete root." , TNORMAL );
		break;
		case CConfFile::ERR_DEL_NODE:
		__show_line_1( "%s%s%s" , TRED , "Delete variable fail." , TNORMAL );
		break;
		case OK:
		break;
		default:
		break;
	}
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////
